package arkham.gui;

import arkham.data.*;
import arkham.data.Board.Location;
import arkham.data.Board.OtherWorld;
import arkham.data.Board.Path;
import arkham.util.AHLogger;
import arkham.util.ImageUtils;
import arkham.util.Pair;
import arkham.util.XmlTools;

import java.awt.*;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

public class BoardManager
{

	private final static int OUTSKIRTS_WIDTH = 110;

	private final static int STACK_NE = 0;
	private final static int STACK_SE = 1;
	private final static int STACK_N = 2;
	private final static int STACK_S = 3;

	private final static int FLOAT_NW = 0;
	private final static int FLOAT_NE = 1;
	private final static int FLOAT_SW = 2;
	private final static int FLOAT_SE = 3;

	public final static int ICON_VORTEX = 1;

	private int width, height;
	private BufferedImage map, base, bgimage, owTexture, overlay, mouseoverBox;
	private BufferedImage sky, outskirts, litas;
	private BufferedImage clueToken, elderSign, locClosed, monsterVortex;
	private GameData gameData;
	private Font bridgeFont, gothicFont, kingFont;
	/** This class stores the rectangular bounds of each street area */
	private HashMap<String, Rectangle2D> streetBounds;
	private List<Pair<? extends Object, Point>> renderList;
	private Pair<? extends Object, Point> mouseTarget;

	/* Fields for using the board as a selection medium */
	private HashMap<String, Integer> locationOverlays;
	private Path displayedPath;

	public BoardManager(int width, int height, GameData gdata)
	{
		this.width = width;
		this.height = height;
		this.gameData = gdata;
		map = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		base = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		overlay = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		mouseoverBox = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		streetBounds = new HashMap<String, Rectangle2D>();
		renderList = new ArrayList<Pair<? extends Object, Point>>();
		mouseTarget = null;
		locationOverlays = new HashMap<String, Integer>();
		initFontsAndImages();
	}

	private void initFontsAndImages()
	{
		bgimage = ImageUtils.loadImage("img/arkhamboard.jpg");
		bgimage = ImageUtils.resizeImage(bgimage, width - OUTSKIRTS_WIDTH, height - 80);
		gothicFont = ImageUtils.loadFont("fonts/gothikka.ttf").deriveFont(
				Font.BOLD, 20);
		bridgeFont = ImageUtils.loadFont("fonts/bridge.ttf").deriveFont(
				Font.PLAIN, 18);
		kingFont = ImageUtils.loadFont("fonts/kingthings.ttf").deriveFont(
				Font.BOLD, 14);
		owTexture = ImageUtils.loadImage("img/otherworld.jpg");
		owTexture = ImageUtils.resizeImage(owTexture, width, 80);
		sky = ImageUtils.loadImage("img/locations/sky.png");
		outskirts = ImageUtils.loadImage("img/locations/outskirts.png");
		litas = ImageUtils.loadImage("img/locations/litas.png");
		ResourceSet rs = gameData.getResourceSet();
		clueToken = ImageUtils.loadImage(rs.getIconFilename("Clue Token"));
		elderSign = ImageUtils.loadImage(rs.getIconFilename("Elder Sign Token"));
		locClosed = ImageUtils.loadImage(rs.getIconFilename("Location Closed Marker"));
		monsterVortex = ImageUtils.loadImage(rs.getIconFilename("Monster Spawn"));
	}

	/**
	 * Gets the image of the map
	 * @return the map image
	 */
	public BufferedImage getBoardImage()
	{
		return map;
	}

	/**
	 * Gets the mouseover overlay
	 * @return the mouseover image
	 */
	public BufferedImage getMouseoverImage()
	{
		return mouseoverBox;
	}

	/**
	 * Gets the object being targeted by the mouse, if any
	 * @return the mouse target
	 */
	public Object getMouseTarget()
	{
		if (mouseTarget == null)
		{
			return null;
		}
		else
		{
			return mouseTarget.getA();
		}
	}

	/**
	 * Updates the map's image to reflect the underlying game data. This also updates
	 * the overlay.
	 */
	public void updateMap()
	{
		Graphics2D g = base.createGraphics();
		g.drawImage(bgimage, 0, 80, null);
		Board board = gameData.getMainBoard();
		drawLocations(g, board);
		drawOtherWorlds(g, board);
		drawOutskirts(g);
		g.dispose();
		updateOverlay();
		g = map.createGraphics();
		g.drawImage(base, 0, 0, null);
		g.drawImage(overlay, 0, 0, null);
		g.dispose();
	}

	/**
	 * Updates the overlay to reflect changes in pieces on the map.
	 */
	public void updateOverlay()
	{
		Graphics2D g = overlay.createGraphics();
		g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
		g.fillRect(0, 0, width, height);
		g.setComposite(AlphaComposite.SrcOver);
		renderList.clear();
		drawOverlayLocations(g);
		drawOverlayOtherWorlds(g);
		drawOverlayOutskirts(g);
		drawOverlaySelection(g);
		g.dispose();
		g = map.createGraphics();
        g.drawImage(base, 0, 0, null);
        g.drawImage(overlay, 0, 0, null);
        g.dispose();
	}

	/**
	 * This is called when the mouseover status is cleared. The board manager will
	 * treat it as if the mouse was not over any object
	 */
	public void clearMouseoverBox()
	{
		Graphics2D g = mouseoverBox.createGraphics();
		g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
		g.fillRect(0, 0, width, height);
		g.setComposite(AlphaComposite.SrcOver);
		g.dispose();
	}

	/**
	 * Gets the location at the mouse position on the board
	 * @param x the mouse x
	 * @param y the mouse y
	 * @return the location at the mouse position, or null if not found
	 */
	public Location getLocationAt(int x, int y)
	{
		Board b = gameData.getMainBoard();
		for (Location loc : b.getLocations())
		{
			if (loc.isStreet())
			{
				Rectangle2D rect = streetBounds.get(loc.getName());
				if (rect.contains(x, y))
				{
					return loc;
				}
			}
			else
			{
				int dx = x - loc.getX();
				int dy = y - loc.getY();
				int dy2 = y - (loc.getY() - 25);
				if ((dx * dx + dy * dy) < 676 || (Math.abs(dx) + Math.abs(dy2)) < 6)
				{
					return loc;
				}
			}
		}
		return null;
	}

	/**
	 * Called by the controller when the mouse is over the board with the specified
	 * location.
	 * @param x the x coordinate of the mouse
	 * @param y the y coordinate of the mouse
	 */
	public void mouseAt(int x, int y)
	{
		/*
		 * This method will go through all objects in the render list
		 * and pick the first one and identify it
		 */
		boolean foundItem = false;
		boolean foundLocation = false;
		for (int i = renderList.size() - 1; i >= 0; i--)
		{
			Object obj = renderList.get(i).getA();
			int px = renderList.get(i).getB().x;
			int py = renderList.get(i).getB().y;
			int dx = x - px;
			int dy = y - py;
			if (obj instanceof Investigator)
			{
				// investigators are 35 x 40
				if (Math.abs(dx) < 18 && Math.abs(dy) < 20)
				{
					foundItem = true;
					// do not redraw if the mouse target has not changed
					if (obj == getMouseTarget())
					{
						break;
					}
					mouseTarget = renderList.get(i);
					mouseOverInvestigator((Investigator)obj, px, py);
					break;
				}
			}
			else if (obj instanceof Monster)
			{
				if (Math.abs(dx) < 18 && Math.abs(dy) < 18)
				{
					foundItem = true;
					if (obj == getMouseTarget())
					{
						break;
					}
					mouseTarget = renderList.get(i);
					mouseOverMonster((Monster)obj, px, py);
					break;
				}
			}
			else if (obj instanceof GateTile)
			{
				if ((dx * dx + dy * dy) < 507)
				{
					foundItem = true;
					if (obj == getMouseTarget())
					{
						break;
					}
					mouseTarget = renderList.get(i);
					mouseOverGate((GateTile)obj, px, py);
				}
			}
		}
		/*
		 * After that, it will go through each location and street
		 */
		Location loc = getLocationAt(x, y);
		if (loc != null)
		{
			foundLocation = true;
			if (loc.isStreet())
			{
				if (!foundItem)
				{
					mouseTarget = new Pair<Object, Point>(loc, new Point(x, y));
				}
			}
			else
			{
				if (!foundItem)
				{
					mouseTarget = new Pair<Object, Point>(loc, new Point(x, y));
				}
				showLocationName(loc);
			}
		}

		if (!foundItem && !foundLocation)
		{
			clearMouseoverBox();
			mouseTarget = null;
		}
	}

	/**
	 * Mouse is over an investigator at position (x, y)
	 * @param inv The investigator being mouseovered
	 * @param x the x coordinate of the investigator
	 * @param y the y coordinate of the investigator
	 */
	private void mouseOverInvestigator(Investigator inv, int x, int y)
	{
		// highlight
		clearMouseoverBox();
		Graphics2D g = mouseoverBox.createGraphics();
		g.setColor(Color.yellow);
		if (inv.isDelayed())
		{
		    g.setClip(x - 20, y - 17, 40, 35);
            g.drawOval(x - 20, y - 24, 48, 48);
            g.setClip(0, 0, width, height);
            g.drawLine(x - 13, y - 17, x + 20, y - 17);
            g.drawLine(x + 20, y - 17, x + 20, y + 17);
            g.drawLine(x - 13, y + 17, x + 20, y + 17);
            // display name
            if (x < width / 2)
            {
                // display box to the right
                drawTextBox(g, inv.getName(), x + 22, y - 17, FLOAT_SE, 2, false);
            }
            else
            {
                // display box to the left
                drawTextBox(g, inv.getName(), x - 22, y - 17, FLOAT_SW, 2, false);
            }
		}
		else
		{
    		g.setClip(x - 17, y - 20, 35, 40);
    		g.drawOval(x - 24, y - 20, 48, 48);
    		g.setClip(0, 0, width, height);
    		g.drawLine(x - 17, y - 13, x - 17, y + 20);
    		g.drawLine(x + 17, y - 13, x + 17, y + 20);
    		g.drawLine(x - 17, y + 20, x + 17, y + 20);
    		// display name
    		if (x < width / 2)
    		{
    			// display box to the right
    			drawTextBox(g, inv.getName(), x + 19, y - 13, FLOAT_SE, 2, false);
    		}
    		else
    		{
    			// display box to the left
    			drawTextBox(g, inv.getName(), x - 19, y - 13, FLOAT_SW, 2, false);
    		}
		}
		g.dispose();
	}

	/**
	 * Mouse is over an monster at position (x, y)
	 * @param inv The monster being mouseovered
	 * @param x the x coordinate of the monster
	 * @param y the y coordinate of the monster
	 */
	private void mouseOverMonster(Monster mon, int x, int y)
	{
		// highlight
		clearMouseoverBox();
		Graphics2D g = mouseoverBox.createGraphics();
		g.setColor(Color.white);
		g.drawRect(x - 17, y - 17, 34, 34);
		// display name
		if (x < width / 2)
		{
			// display box to the right
			drawTextBox(g, mon.getName(), x + 19, y - 17, FLOAT_SE, 2, false);
		}
		else
		{
			// display box to the left
			drawTextBox(g, mon.getName(), x - 19, y - 17, FLOAT_SW, 2, false);
		}
		g.dispose();
	}

	/**
	 * Mouse is over a gate at position (x, y)
	 * @param inv The gate being mouseovered
	 * @param x the x coordinate of the gate
	 * @param y the y coordinate of the gate
	 */
	private void mouseOverGate(GateTile gate, int x, int y)
	{
		// highlight
		clearMouseoverBox();
		Graphics2D g = mouseoverBox.createGraphics();
		g.setColor(Color.red);
		g.setStroke(new BasicStroke(2, BasicStroke.JOIN_ROUND, BasicStroke.JOIN_ROUND));
		g.drawOval(x - 22, y - 22, 45, 45);
		// display details
		// display box to the right
		Font f = new Font("Arial", Font.PLAIN, 14);
		g.setFont(f);
		FontMetrics fm = g.getFontMetrics();
		String[] text = ImageUtils.splitString(gate.getName(), 2, g, f, false);
		int w = 55, h;
		h = (1 + text.length) * fm.getHeight();
		int[] widths = new int[text.length];
		for (int i = 0; i < text.length; i++)
		{
			Rectangle2D bounds = fm.getStringBounds(text[i], g);
			widths[i] = (int)Math.round(bounds.getWidth());
			w = Math.max(w, widths[i]);
		}
		int tx, ty;
		if (x < width / 2)
		{
			if (y > (2 * height / 3))
			{
				tx = x + 22;
				ty = y + 22 - h;
			}
			else
			{
				tx = x + 22;
				ty = y - 22;
			}
		}
		else
		{
			if (y > (2 * height / 3))
			{
				tx = x - 22 - w;
				ty = y + 22 - h;
			}
			else
			{
				tx = x - 22 - w;
				ty = y - 22;
			}
		}
		g.setColor(new Color(250, 250, 192));
		g.fillRect(tx - 2, ty, w + 3, h + 1);
		g.setColor(Color.black);
		g.drawRect(tx - 2, ty, w + 3, h + 1);
		for (int i = 0; i < text.length; i++)
		{
			g.drawString(text[i], tx + (w - widths[i]) / 2,
					ty + fm.getAscent() + fm.getHeight() * i);
		}
		g.setColor(Color.red);
		g.drawString(XmlTools.getModifier(gate.getModifier()),
				tx + (w - 30) / 2, ty + text.length * fm.getHeight() + fm.getAscent());
		g.drawImage(gate.getSymbol().getImage(), tx + (w + 10) / 2,
				ty + text.length * fm.getHeight() + 2, null);
		g.dispose();
	}

	/**
	 * Sets an overlay above a location
	 * @param locName the location name
	 * @param overlayType the overlay icon constant
	 */
	public void setLocationOverlay(String locName, int overlayType)
	{
		locationOverlays.put(locName, overlayType);
	}

	/**
	 * Sets the displayed path
     * @param displayedPath the displayedPath to set
     */
    public void setDisplayedPath(Path displayedPath)
    {
        this.displayedPath = displayedPath;
    }

    /**
     * Clears the displayed path
     */
    public void clearDisplayedPath()
    {
        displayedPath = null;
    }

    /**
     * Gets the displayed path
     * @return the displayedPath
     */
    public Path getDisplayedPath()
    {
        return displayedPath;
    }

    /**
	 * Clears the overlay above a location
	 * @param locName the location name
	 */
	public void clearLocationOverlay(String locName)
	{
		locationOverlays.remove(locName);
	}

	/**
	 * Clears all the overlays above locations.
	 */
	public void clearAllLocationOverlays()
	{
		locationOverlays.clear();
	}

	/**
	 * Shows a location's name under the location
	 * @param loc the location to show
	 */
	private void showLocationName(Location loc)
	{
		if (loc.isStreet())
		{
			return;
		}
		Graphics2D g = mouseoverBox.createGraphics();
		g.setComposite(AlphaComposite.DstOver);
		g.setFont(gothicFont.deriveFont(Font.BOLD, 16));
		FontMetrics fm = g.getFontMetrics();
		Rectangle2D bounds = fm.getStringBounds(loc.getDescription(), g);
		int w = (int)Math.round(bounds.getWidth());
		int h = (int)Math.round(bounds.getHeight());
		int tx = loc.getX() - w / 2;
		tx = Math.max(0, tx);
		tx = Math.min(width - w - OUTSKIRTS_WIDTH, tx);
		int ty = loc.getY() + 26;
		g.setColor(ImageUtils.getContrastingColor(loc.getColor()));
		g.drawString(loc.getDescription(), tx, ty + fm.getAscent());
		g.drawRect(tx - 2, ty, w + 3, h + 1);
		g.setColor(loc.getColor());
		g.fillRect(tx - 2, ty, w + 3, h + 1);
		g.dispose();
	}

	/**
	 * Draws the locations on the board
	 * @param g the Graphics context
	 * @param b the board to draw
	 */
	private void drawLocations(Graphics2D g, Board b)
	{
		FontMetrics fm = g.getFontMetrics(gothicFont);
		g.setFont(gothicFont);


		// draw locations and streets
		for (Location loc : b.getLocations())
		{
			g.setColor(loc.getColor());
			if (!loc.isStreet())
			{
				g.drawImage(loc.getImage(), loc.getX() - 25, loc.getY() - 25, null);
				g.setStroke(new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
				g.drawOval(loc.getX() - 25, loc.getY() - 25, 50, 50);
				// draw diamond
				if (loc.isStable())
				{
					g.setColor(new Color(22, 147, 26));
				}
				else
				{
					g.setColor(new Color(209, 25, 17));
				}
				g.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER));
				drawDiamond(g, loc.getX(), loc.getY() - 22, 5);
			}
			else
			{
				Rectangle2D bounds = fm.getStringBounds(loc.getDescription(), g);
				int boxWidth = (int)Math.round(bounds.getWidth()) + 6;
				int boxHeight = (int)Math.round(bounds.getHeight()) + 6;
				streetBounds.put(loc.getName(), new Rectangle2D.Double(loc.getX() - boxWidth / 2 - 1,
						loc.getY() - boxHeight / 2 - 1, boxWidth + 2, boxHeight + 2));
				g.setStroke(new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
				g.fillRect(loc.getX() - boxWidth / 2, loc.getY() - boxHeight / 2, boxWidth, boxHeight);
				g.setColor(Color.black);
				g.drawRect(loc.getX() - boxWidth / 2, loc.getY() - boxHeight / 2, boxWidth, boxHeight);

				g.drawString(loc.getDescription(), loc.getX() - boxWidth / 2 + 3,
						loc.getY() - boxHeight / 2 + 3 + fm.getMaxAscent());
			}
		}

		// draw connectors
		HashSet<String> used = new HashSet<String>();

		for (int i = 0; i < b.getLocations().size(); i++)
		{
			Location loc1 = b.getLocations().get(i);
			for (Location loc2 : loc1.getEdges())
			{
				if (used.contains(loc2.getName()))
				{
					continue;
				}
				Point p1 = loc1.getEdgeConnectorTo(loc2.getName());
				Point p2 = loc2.getEdgeConnectorTo(loc1.getName());
				if (p1.x == 0 && p1.y == 0)
				{
					continue;
				}
				g.setStroke(new BasicStroke(3, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER));
				g.setColor(new Color(209, 236, 23));
				g.drawLine(p1.x, p1.y, p2.x, p2.y);
				g.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER));
				// draw arrows from loc1 to loc2
				if (loc1.getBlackEdge().equals(loc2))
				{
					if (loc1.getWhiteEdge().equals(loc2))
					{
						// both arrows
						drawHalfArrows(g, p1.x, p1.y, p2.x, p2.y, 6);
					}
					else
					{
						drawArrow(g, p1.x, p1.y, p2.x, p2.y, 6, Color.black, Color.white);
					}
				}
				else if (loc1.getWhiteEdge().equals(loc2))
				{
					drawArrow(g, p1.x, p1.y, p2.x, p2.y, 6, Color.white, Color.black);
				}
				// draw reverse arrows
				if (loc2.getBlackEdge().equals(loc1))
				{
					if (loc2.getWhiteEdge().equals(loc1))
					{
						// both arrows
						drawHalfArrows(g, p2.x, p2.y, p1.x, p1.y, 6);
					}
					else
					{
						drawArrow(g, p2.x, p2.y, p1.x, p1.y, 6, Color.black, Color.white);
					}
				}
				else if (loc2.getWhiteEdge().equals(loc1))
				{
					drawArrow(g, p2.x, p2.y, p1.x, p1.y, 6, Color.white, Color.black);
				}

			}

			used.add(loc1.getName());
		}
	}

	/**
	 * Draw the other worlds on the board
	 * @param g the Graphics context
	 * @param b the board to draw
	 */
	private void drawOtherWorlds(Graphics2D g, Board b)
	{

		g.drawImage(owTexture, 0, 0, null);
		g.setFont(new Font("Serif", Font.BOLD, 12));
		FontMetrics fm = g.getFontMetrics();

		for (OtherWorld ow : b.getOtherWorlds())
		{
			// draw the name of the other world
			Rectangle2D bounds = fm.getStringBounds(ow.getDescription(), g);
			int xpos = (int)Math.round(ow.getX() - bounds.getWidth() / 2);
			int ypos = (int)Math.round(ow.getY() - bounds.getHeight() / 2 + fm.getMaxAscent());
			g.setColor(new Color(252, 252, 200));
			g.fillRoundRect(ow.getX() - 37, ow.getY() - 35, 74, 15, 5, 5);
			g.setColor(Color.black);
			g.drawRoundRect(ow.getX() - 37, ow.getY() - 35, 74, 15, 5, 5);
			g.drawString(ow.getDescription(), xpos, ypos - 28);

			// draw the colored circles
			String csString = ow.getColorSet().toString();
			if (csString.equals("All"))
			{
				g.setColor(XmlTools.translateGateColor("red"));
				g.fillOval(ow.getX() + 25, ow.getY() - 17, 8, 8);
				g.setColor(XmlTools.translateGateColor("yellow"));
				g.fillOval(ow.getX() + 25, ow.getY() - 7, 8, 8);
				g.setColor(XmlTools.translateGateColor("blue"));
				g.fillOval(ow.getX() + 35, ow.getY() - 17, 8, 8);
				g.setColor(XmlTools.translateGateColor("green"));
				g.fillOval(ow.getX() + 35, ow.getY() - 7, 8, 8);
				g.setColor(Color.black);
				g.drawOval(ow.getX() + 25, ow.getY() - 17, 8, 8);
				g.drawOval(ow.getX() + 25, ow.getY() - 7, 8, 8);
				g.drawOval(ow.getX() + 35, ow.getY() - 17, 8, 8);
				g.drawOval(ow.getX() + 35, ow.getY() - 7, 8, 8);
			}
			else
			{
				String[] colors = csString.split("/");
				g.setColor(XmlTools.translateGateColor(colors[0]));
				g.fillOval(ow.getX() + 31, ow.getY() - 17, 8, 8);
				g.setColor(XmlTools.translateGateColor(colors[1]));
				g.fillOval(ow.getX() + 31, ow.getY() - 7, 8, 8);
				g.setColor(Color.black);
				g.drawOval(ow.getX() + 31, ow.getY() - 17, 8, 8);
				g.drawOval(ow.getX() + 31, ow.getY() - 7, 8, 8);
			}
		}
	}

	/**
	 * Draw the outskirts, sky and litas on the board
	 * @param g the Graphics context
	 */
	private void drawOutskirts(Graphics2D g)
	{
		g.setColor(new Color(255, 128, 0));
		g.fillRect(width - OUTSKIRTS_WIDTH, 80, OUTSKIRTS_WIDTH, height - 80);
		g.setColor(new Color(100, 50, 0));
		g.setStroke(new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
		g.drawRect(width - OUTSKIRTS_WIDTH + 1, 81, OUTSKIRTS_WIDTH - 1, height - 82);

		// From top to bottom: LITAS, sky, outskirts
		int xp = width - OUTSKIRTS_WIDTH + 1;
		int yp = 81;
		g.drawImage(litas, xp + 10, yp + 40, null);
		g.drawImage(sky, xp + 10, yp + 155, null);
		g.drawImage(outskirts, xp + 10, yp + 270, null);
		g.setColor(Color.black);
		g.setFont(bridgeFont.deriveFont(Font.PLAIN, 14));
		g.drawString("Lost in Time", xp + 18, yp + 15);
		g.drawString("and Space", xp + 24, yp + 31);
		g.setFont(bridgeFont);
		g.drawString("Sky", xp + 39, yp + 148);
		g.drawString("Outskirts", xp + 23, yp + 263);
	}

	/**
	 * Draw the overlay for each location. Also stores where each item is
	 * drawn for easy reference on mouseovers
	 * @param g the Graphics context to draw on
	 */
	private void drawOverlayLocations(Graphics2D g)
	{
		for (Location loc : gameData.getMainBoard().getLocations())
		{
			List<Investigator> invlist = loc.getInvestigatorList();
			List<Monster> monlist = loc.getMonsterList();
			// split into cases for rendering purposes
			int count = 0;
			int xmod = 0;
			int ymod = 0;
			if (loc.isSealed())
			{
				count++;
			}
			if (loc.isClosed())
			{
				count++;
			}
			if (loc.getNumClues() > 0)
			{
				count++;
			}
			if (invlist.size() > 0)
			{
				count++;
			}
			if (monlist.size() > 0)
			{
				count++;
			}
			if (loc.hasOpenGate())
			{
				drawCenteredImage(g, loc.getGate().getImage(), loc.getX(), loc.getY());
				addToRenderList(loc.getGate(), loc.getX(), loc.getY());
				ymod = 8;
			}
			if (loc.isStreet())
			{
				xmod = 4;
			}
			// check how many things to render
			if (count == 1)
			{
				// render in the middle
				if (loc.isSealed())
				{
					drawCenteredImage(g, elderSign, loc.getX(), loc.getY());
				}
				if (loc.isClosed())
				{
					drawCenteredImage(g, locClosed, loc.getX(), loc.getY());
				}
				if (loc.getNumClues() == 1)
				{
					drawCenteredImage(g, clueToken, loc.getX(), loc.getY());
				}
				else if (loc.getNumClues() > 1)
				{
					drawStackableIcon(g, clueToken, clueToken, loc.getX(), loc.getY(),
							loc.getNumClues(), STACK_SE, Color.yellow, new Color(192, 156, 0));
				}
				if (invlist.size() == 1)
				{
				    Investigator inv = invlist.get(0);
				    drawCenteredImage(g, inv.getDisplayImage(), loc.getX(), loc.getY() + ymod);
				    addToRenderList(invlist.get(0), loc.getX(), loc.getY() + ymod);
				}
				else if (invlist.size() > 1)
				{
					drawStackableIcon(g, invlist.get(0).getDisplayImage(),
					        invlist.get(1).getDisplayImage(), loc.getX(), loc.getY() + ymod,
					        invlist.size(), STACK_NE, Color.white, Color.gray);
					addToRenderList(invlist.get(0), loc.getX() + 2, loc.getY() - 2 + ymod);
				}
				if (monlist.size() == 1)
				{
					drawCenteredImage(g, monlist.get(0).getImage(), loc.getX(), loc.getY() + ymod);
					addToRenderList(monlist.get(0), loc.getX(), loc.getY() + ymod);
				}
				else if (monlist.size() > 1)
				{
					drawStackableIcon(g, monlist.get(0).getImage(), monlist.get(1).getImage(),
							loc.getX(), loc.getY() + ymod,
							monlist.size(), STACK_NE,
							monlist.get(0).getTextColor(), Color.gray);
					addToRenderList(monlist.get(0), loc.getX() + 2, loc.getY() - 2 + ymod);
				}
			}
			else if (count >= 2)
			{
				if (monlist.size() > 0 && invlist.size() > 0)
				{
					// monsters and investigators present
					// always draw investigators on the left
					// and monsters on the right
					if (monlist.size() == 1)
					{
						drawCenteredImage(g, monlist.get(0).getImage(),
								loc.getX() + 17 + xmod, loc.getY() + ymod);
						addToRenderList(monlist.get(0), loc.getX() + 17 + xmod, loc.getY() + ymod);
					}
					else if (monlist.size() > 1)
					{
						drawStackableIcon(g, monlist.get(0).getImage(), monlist.get(1).getImage(),
								loc.getX() + 17 + xmod, loc.getY() + ymod,
								monlist.size(), STACK_N,
								monlist.get(0).getTextColor(), Color.gray);
						addToRenderList(monlist.get(0), loc.getX() + 17 + xmod, loc.getY() - 2 + ymod);
					}
					if (invlist.size() == 1)
					{
						drawCenteredImage(g, invlist.get(0).getDisplayImage(),
								loc.getX() - 18 - xmod, loc.getY() + ymod);
						addToRenderList(invlist.get(0), loc.getX() - 18 - xmod, loc.getY() + ymod);
					}
					else if (invlist.size() > 1)
					{
						drawStackableIcon(g, invlist.get(0).getDisplayImage(),
						        invlist.get(1).getDisplayImage(), loc.getX() - 18 - xmod, loc.getY() + ymod, invlist.size(), STACK_N,
								Color.white, Color.gray);
						addToRenderList(invlist.get(0), loc.getX() - 18 - xmod, loc.getY() - 2 + ymod);
					}
				}
				else if (monlist.size() > 0)
				{
					// monsters and some other tokens present
					// draw monsters on the right

					if (loc.isSealed())
					{
						if (loc.getNumClues() > 0)
						{
							drawCenteredImage(g, elderSign, loc.getX() - 15, loc.getY() + 10);
						}
						else
						{
							drawCenteredImage(g, elderSign, loc.getX() - 15, loc.getY());
						}
					}
					if (loc.getNumClues() == 1)
					{
						drawCenteredImage(g, clueToken, loc.getX() - 15, loc.getY() - 5);
					}
					else if (loc.getNumClues() > 1)
					{
						drawStackableIcon(g, clueToken, clueToken, loc.getX() - 15, loc.getY() - 5,
								loc.getNumClues(), STACK_N, Color.yellow, new Color(192, 156, 0));
					}
					if (monlist.size() == 1)
					{
						drawCenteredImage(g, monlist.get(0).getImage(),
								loc.getX() + 12 + xmod, loc.getY() + ymod);
						addToRenderList(monlist.get(0), loc.getX() + 12 + xmod, loc.getY() + ymod);
					}
					else if (monlist.size() > 1)
					{
						drawStackableIcon(g, monlist.get(0).getImage(), monlist.get(1).getImage(),
								loc.getX() + 12 + xmod, loc.getY() + ymod,
								monlist.size(), STACK_N,
								monlist.get(0).getTextColor(), Color.gray);
						addToRenderList(monlist.get(0), loc.getX() + 12 + xmod, loc.getY() - 2 + ymod);
					}
				}
				else if (invlist.size() > 0)
				{
					// investigators and some other tokens present
					// always draw investigators on the left
					if (loc.isSealed())
					{
						if (loc.getNumClues() > 0)
						{
							drawCenteredImage(g, elderSign, loc.getX() + 15, loc.getY() + 10);
						}
						else
						{
							drawCenteredImage(g, elderSign, loc.getX() + 15, loc.getY());
						}
					}
					if (loc.getNumClues() == 1)
					{
						drawCenteredImage(g, clueToken, loc.getX() + 15, loc.getY() - 5);
					}
					else if (loc.getNumClues() > 1)
					{
						drawStackableIcon(g, clueToken, clueToken,
								loc.getX() + 15, loc.getY() - 5,
								loc.getNumClues(), STACK_N, Color.yellow, new Color(192, 156, 0));
					}
					if (invlist.size() == 1)
					{
						drawCenteredImage(g, invlist.get(0).getDisplayImage(), loc.getX() - 12 - xmod,
						        loc.getY());
						addToRenderList(invlist.get(0), loc.getX() - 12 - xmod, loc.getY() + ymod);
					}
					else if (invlist.size() > 1)
					{
						drawStackableIcon(g, invlist.get(0).getDisplayImage(),
						        invlist.get(1).getDisplayImage(),loc.getX() - 12 - xmod,
						        loc.getY() + ymod, invlist.size(), STACK_N, Color.white, Color.gray);
						addToRenderList(invlist.get(0), loc.getX() - 12 - xmod, loc.getY() - 2 + ymod);
					}
				}
				else if (loc.isClosed())
				{
					// location closed. Draw the closed on the left
					drawCenteredImage(g, locClosed, loc.getX() - 10, loc.getY());
					if (loc.isSealed())
					{
						if (loc.getNumClues() > 0)
						{
							drawCenteredImage(g, elderSign, loc.getX() + 15, loc.getY() + 10);
						}
						else
						{
							drawCenteredImage(g, elderSign, loc.getX() + 15, loc.getY());
						}
					}
					if (loc.getNumClues() == 1)
					{
						drawCenteredImage(g, clueToken, loc.getX() + 15, loc.getY() - 5);
					}
					else if (loc.getNumClues() > 1)
					{
						drawStackableIcon(g, clueToken, clueToken,
								loc.getX() + 15, loc.getY() - 5 + ymod,
								loc.getNumClues(), STACK_N, Color.yellow, new Color(192, 156, 0));
					}
				}
				else
				{
					// no investigators or monsters
					// investigators and some other tokens present
					// always draw investigators on the left
					if (loc.isSealed())
					{
						if (loc.getNumClues() > 0)
						{
							drawCenteredImage(g, elderSign, loc.getX(), loc.getY() + 10);
						}
						else
						{
							drawCenteredImage(g, elderSign, loc.getX(), loc.getY());
						}
					}
					if (loc.getNumClues() == 1)
					{
						drawCenteredImage(g, clueToken, loc.getX(), loc.getY() - 5);
					}
					else if (loc.getNumClues() > 1)
					{
						drawStackableIcon(g, clueToken, clueToken,
								loc.getX(), loc.getY() - 5 + ymod,
								loc.getNumClues(), STACK_N, Color.yellow, new Color(192, 156, 0));
					}
				}
			}// end of if count >= 2
		} // end of foreach location
	}

	/**
	 * Draw the overlay for other worlds
	 * @param g the graphics context
	 */
	private void drawOverlayOtherWorlds(Graphics2D g)
	{
		Board b = gameData.getMainBoard();

		for (OtherWorld ow : b.getOtherWorlds())
		{
			for (int i = 1; i <= 2; i++)
			{
				LocationContainer lcon = ow.getSegment(i);
				List<Investigator> invlist = lcon.getInvestigatorList();
				int xmod = (i == 1) ? -19 : 18;
				if (invlist.size() == 1)
				{
					drawCenteredImage(g, invlist.get(0).getDisplayImage(), ow.getX() + xmod,
					        ow.getY() + 5);
					addToRenderList(invlist.get(0), ow.getX() + xmod, ow.getY() + 5);
				}
				else if (invlist.size() > 1)
				{
					drawStackableIcon(g, invlist.get(0).getDisplayImage(),
					        invlist.get(1).getDisplayImage(), ow.getX() + xmod, ow.getY() + 7,
					        invlist.size(), STACK_N, Color.white, Color.gray);
					addToRenderList(invlist.get(0), ow.getX() + xmod, ow.getY() + 5);
				}
			}
		}
	}

	/**
	 * Draw the overlay for the outskirts panel. Note that
	 * only investigators can be in LITAS and monsters can be in
	 * outskirts and sky.
	 * @param g The graphics context
	 */
	private void drawOverlayOutskirts(Graphics2D g)
	{
		Board b = gameData.getMainBoard();

		int xp = width - OUTSKIRTS_WIDTH + 1;
		int yp = 81;

		// litas
		LocationContainer loc = b.getLocationContainer(Board.LITAS_NAME);
		List<Investigator> invlist = loc.getInvestigatorList();
		if (invlist.size() == 1)
		{
			drawCenteredImage(g, invlist.get(0).getDisplayImage(), xp + 55, yp + 85);
			addToRenderList(invlist.get(0), xp + 55, yp + 85);
		}
		else if (invlist.size() > 1)
		{
			drawStackableIcon(g, invlist.get(0).getDisplayImage(), invlist.get(1).getDisplayImage(),
					xp + 55, yp + 85, invlist.size(), STACK_NE,
					Color.white, Color.gray);
			addToRenderList(invlist.get(0), xp + 57, yp + 83);
		}
		// sky
		loc = b.getLocationContainer(Board.SKY_NAME);
		List<Monster> monlist = loc.getMonsterList();
		if (monlist.size() == 1)
		{
			drawCenteredImage(g, monlist.get(0).getImage(), xp + 55, yp + 200);
			addToRenderList(monlist.get(0), xp + 55, yp + 200);
		}
		else if (monlist.size() > 1)
		{
			drawStackableIcon(g, monlist.get(0).getImage(), monlist.get(1).getImage(),
					xp + 55, yp + 200, monlist.size(), STACK_NE,
					monlist.get(0).getTextColor(), Color.gray);
			addToRenderList(monlist.get(0), xp + 57, yp + 198);
		}
		// outskirts
		loc = b.getLocationContainer(Board.OUTSKIRTS_NAME);
		monlist = loc.getMonsterList();
		if (monlist.size() == 1)
		{
			drawCenteredImage(g, monlist.get(0).getImage(), xp + 55, yp + 315);
			addToRenderList(monlist.get(0), xp + 55, yp + 315);
		}
		else if (monlist.size() > 1)
		{
			drawStackableIcon(g, monlist.get(0).getImage(), monlist.get(1).getImage(),
					xp + 55, yp + 315, monlist.size(), STACK_NE,
					monlist.get(0).getTextColor(), Color.gray);
			addToRenderList(monlist.get(0), xp + 57, yp + 313);
		}
	}

	/**
	 * Draws the overlay for selection and locations, this overlay goes above
	 * the other overlays.
	 */
	private void drawOverlaySelection(Graphics2D g)
	{
		Board b = gameData.getMainBoard();
		for (Map.Entry<String, Integer> entry : locationOverlays.entrySet())
		{
			Location loc = b.getLocation(entry.getKey());
			if (loc == null) continue;
			switch (entry.getValue())
			{
			case ICON_VORTEX:
				g.drawImage(monsterVortex, loc.getX() - monsterVortex.getWidth()/2,
						loc.getY() - monsterVortex.getHeight()/2, null);
				break;
			default:
				continue;
			}
		}
		if (displayedPath != null && displayedPath.getPath().size() > 0)
		{
		    Location src = displayedPath.getOrigin();
		    int i = 0;
		    g.setColor(Color.green);
		    while (i < displayedPath.getPath().size())
		    {
		        Location dest = displayedPath.getPath().get(i);
		        Point ps = src.getEdgeConnectorTo(dest.getName());
		        Point pd = dest.getEdgeConnectorTo(src.getName());
		        if (ps == null || pd == null)
		        {
		            AHLogger.warning("drawOverlaySelection",
		                    displayedPath + ": " + src + "->" + dest);
		            break;
		        }
		        double dist = Point2D.distance(ps.x, ps.y, pd.x, pd.y);
		        double normdx = (pd.x - ps.x) / dist;
		        double normdy = (pd.y - ps.y) / dist;
		        double cx = (ps.x + pd.x) / 2.0;
		        double cy = (ps.y + pd.y) / 2.0;
		        Color fill = displayedPath.isNodeBlocked(i) ? Color.red : Color.green;
		        int aw = 10;
		        drawArrow(g, cx - aw * normdx, cy - aw * normdy, cx + aw * normdx, cy + aw * normdy,
		                aw, fill, Color.black);
		        src = dest;
		        i++;
		    }
		}
	}

	/* ----------------------- UTILITY METHODS --------------------- */
	/**
	 * Draw an arrow with a border from one point to another
	 * @param g The graphics context
	 * @param x1 x coordinate of 1st point
	 * @param y1 y coordinate of 1st point
	 * @param x2 x coordinate of 2nd point
	 * @param y2 y coordinate of 2nd point
	 * @param width the width of the arrow
	 * @param fill the color of the interior of the arrow
	 * @param border the border color of the arrow
	 */
	private void drawArrow(Graphics2D g, double x1, double y1, double x2, double y2, int width,
			Color fill, Color border)
	{

		double dist = Point2D.distance(x1, y1, x2, y2);
		double vx = (x2 - x1) / dist;
		double vy = (y2 - y1) / dist;

		int[] xp = new int[] {
				(int)Math.round(x1 + vx * width / 3),
				(int)Math.round(x1 + vy * width),
				(int)Math.round(x1 + vx * width * 2),
				(int)Math.round(x1 - vy * width)
		};
		int[] yp = new int[] {
				(int)Math.round(y1 + vy * width / 3),
				(int)Math.round(y1 - vx * width),
				(int)Math.round(y1 + vy * width * 2),
				(int)Math.round(y1 + vx * width)
		};

		Polygon poly = new Polygon(xp, yp, 4);
		g.setColor(fill);
		g.fillPolygon(poly);
		g.setColor(border);
		g.drawPolygon(poly);
	}

	/**
	 * Draws black/white half arrows from one point to another
	 * @param g the graphics context to draw on
	 * @param x1 x coordinate of 1st point
	 * @param y1 y coordinate of 1st point
	 * @param x2 x coordinate of 2nd point
	 * @param y2 y coordinate of 2nd point
	 * @param width the width of the arrow
	 */
	private void drawHalfArrows(Graphics2D g, double x1, double y1, double x2, double y2, int width)
	{
		double dist = Point2D.distance(x1, y1, x2, y2);
		double vx = (x2 - x1) / dist;
		double vy = (y2 - y1) / dist;

		int[] xp1 = new int[] {
				(int)Math.round(x1 + vx * width / 3),
				(int)Math.round(x1 + vy * width),
				(int)Math.round(x1 + vx * width * 2)
		};
		int[] yp1 = new int[] {
				(int)Math.round(y1 + vy * width / 3),
				(int)Math.round(y1 - vx * width),
				(int)Math.round(y1 + vy * width * 2)
		};
		int[] xp2 = new int[] {
				(int)Math.round(x1 + vx * width / 3),
				(int)Math.round(x1 + vx * width * 2),
				(int)Math.round(x1 - vy * width)
		};
		int[] yp2 = new int[] {
				(int)Math.round(y1 + vy * width / 3),
				(int)Math.round(y1 + vy * width * 2),
				(int)Math.round(y1 + vx * width)
		};

		Polygon poly1 = new Polygon(xp1, yp1, 3);
		Polygon poly2 = new Polygon(xp2, yp2, 3);
		g.setColor(Color.black);
		g.fillPolygon(poly1);
		g.setColor(Color.white);
		g.fillPolygon(poly2);
		g.drawLine(xp1[0], yp1[0], xp1[1], yp1[1]);
		g.drawLine(xp1[1], yp1[1], xp1[2], yp1[2]);
		g.setColor(Color.black);
		g.drawPolygon(poly2);
	}

	/**
	 * Draw a diamond at the specified location
	 * @param g the Graphics context
	 * @param x the x coordinate
	 * @param y the y coordinate
	 * @param width the width
	 */
	private void drawDiamond(Graphics g, int x, int y, int width)
	{
		int[] xp = new int[] {x - width, x, x + width, x};
		int[] yp = new int[] {y, y - width, y, y + width};
		Polygon poly = new Polygon(xp, yp, 4);
		g.fillPolygon(poly);
		g.setColor(Color.black);
		g.drawPolygon(poly);
	}

	/**
	 * Draw a stack of icons based on quantity
	 * @param g The graphics context
	 * @param image the image to draw
	 * @param image the image to draw behind in the stack
	 * @param x the x coordinate of the center point
	 * @param y the y coordinate of the center point
	 * @param qty the number of icons. Must be more than 1
	 * @param textColor the color of the text
	 * @param pos The position to draw it at, one of the STACK constants
	 */
	private void drawStackableIcon(Graphics g, BufferedImage image, BufferedImage image2,
			int x, int y, int qty, int pos, Color textColor, Color textColor2)
	{
		if (qty < 2 || qty > 99) return;
		int cx = x - image.getWidth()/2;
		int cy = y - image.getHeight()/2;

		g.setFont(new Font("Arial", Font.BOLD, 12));
		FontMetrics fm = g.getFontMetrics();
		Rectangle2D bounds = fm.getStringBounds(String.valueOf(qty), g);

		if (pos == STACK_NE)
		{
			g.drawImage(image2, cx - 2, cy + 2, null);
			g.drawImage(image, cx + 2, cy - 2, null);
			int tx = (int)Math.round(x - bounds.getX());
			int ty = (int)Math.round(y - bounds.getHeight() / 2);
			g.setColor(textColor2);
			g.drawString(String.valueOf(qty), tx + 1, ty + fm.getAscent() + 1);
			g.setColor(textColor);
			g.drawString(String.valueOf(qty), tx, ty + fm.getAscent());
		}
		else if (pos == STACK_SE)
		{
			g.drawImage(image2, cx - 2, cy - 2, null);
			g.drawImage(image, cx + 2, cy + 2, null);
			int tx = (int)Math.round(x - bounds.getX());
			int ty = (int)Math.round(y - bounds.getHeight() / 2);
			g.setColor(textColor2);
			g.drawString(String.valueOf(qty), tx + 1, ty + fm.getAscent() + 1);
			g.setColor(textColor);
			g.drawString(String.valueOf(qty), tx, ty + fm.getAscent());
		}
		else if (pos == STACK_N)
		{
			g.drawImage(image2, cx, cy + 2, null);
			g.drawImage(image, cx, cy - 2, null);
			int tx = (int)Math.round(x - bounds.getX());
			int ty = (int)Math.round(y - bounds.getHeight() / 2);
			g.setColor(textColor2);
			g.drawString(String.valueOf(qty), tx + 1, ty + fm.getAscent() + 1);
			g.setColor(textColor);
			g.drawString(String.valueOf(qty), tx, ty + fm.getAscent());
		}
		else if (pos == STACK_S)
		{
			g.drawImage(image2, cx, cy - 2, null);
			g.drawImage(image, cx, cy + 2, null);
			int tx = (int)Math.round(x - bounds.getX());
			int ty = (int)Math.round(y - bounds.getHeight() / 2);
			g.setColor(textColor2);
			g.drawString(String.valueOf(qty), tx + 1, ty + fm.getAscent() + 1);
			g.setColor(textColor);
			g.drawString(String.valueOf(qty), tx, ty + fm.getAscent());
		}
	}

	/**
	 * Draw a centered image
	 * @param g The graphics context
	 * @param image the image to draw
	 * @param x the x coordinate of the center point
	 * @param y the y coordinate of the center point
	 */
	private void drawCenteredImage(Graphics g, BufferedImage image, int x, int y)
	{
		if (image != null)
		{
			g.drawImage(image, x - image.getWidth()/2, y - image.getHeight()/2, null);
		}
	}

	/**
	 * Draws a text box with the specified string in specified lines
	 * @param g the graphics context
	 * @param text the text to write in array form
	 * @param x the x coordinate
	 * @param y the y coordinate
	 * @param pos the position indicator, one of the FLOAT constants
	 * @param textColor the text color, also the border color
	 * @param bgColor the background color
	 */
	private void drawTextBox(Graphics2D g, String[] text, int x, int y, int pos,
			Color textColor, Color bgColor)
	{
		// split up the string
		g.setFont(new Font("Serif", Font.PLAIN, 14));
		int[] widths = new int[text.length];
		FontMetrics fm = g.getFontMetrics();
		int boxHeight = fm.getHeight() * text.length;
		int boxWidth = 0;
		for (int i = 0; i < text.length; i++)
		{
			Rectangle2D bounds = fm.getStringBounds(text[i], g);
			widths[i] = (int)Math.round(bounds.getWidth());
			boxWidth = Math.max(boxWidth, widths[i]);
		}
		g.setColor(bgColor);
		int tx = x;
		int ty = y;
		// draw the box and the words
		if (pos == FLOAT_SW)
		{
			tx = x - boxWidth;
		}
		else if (pos == FLOAT_NE)
		{
			ty = y - boxHeight;
		}
		else if (pos == FLOAT_NW)
		{
			tx = x - boxWidth;
			ty = y - boxHeight;
		}
		g.fillRect(tx - 2, ty - 1, boxWidth + 3, boxHeight + 2);
		g.setColor(textColor);
		g.drawRect(tx - 2, ty - 1, boxWidth + 3, boxHeight + 2);
		for (int i = 0; i < text.length; i++)
		{
			g.drawString(text[i], tx + (boxWidth - widths[i]) / 2,
					ty + fm.getHeight() * i + fm.getAscent());
		}
	}

	/**
	 * Draw a text box with default colors
	 * @param g the graphics context
	 * @param text the text to write
	 * @param x the x coordinate
	 * @param y the y coordinate
	 * @param pos the position indicator, one of the FLOAT constants
	 * @param prefLines the preferred number of lines
	 * @param splitDashes true to count dashes as word boundaries, false to
	 * count them as word characters
	 */
	private void drawTextBox(Graphics2D g, String text, int x, int y, int pos,
			int prefLines, boolean splitDashes)
	{
		drawTextBox(g, text, x, y, pos, prefLines, splitDashes,
				Color.black, new Color(250, 250, 192));
	}

	/**
	 * Draw a text box with specified text and background color
	 * @param g the graphics context
	 * @param text the text to write
	 * @param x the x coordinate
	 * @param y the y coordinate
	 * @param pos the position indicator, one of the FLOAT constants
	 * @param prefLines the preferred number of lines
	 * @param splitDashes true to count dashes as word boundaries, false to
	 * count them as word characters
	 * @param textColor the text color, also the border color
	 * @param bgColor the background color
	 */
	private void drawTextBox(Graphics2D g, String text, int x, int y, int pos,
			int prefLines, boolean splitDashes, Color textColor, Color bgColor)
	{
		// split up the string
		g.setFont(new Font("Arial", Font.PLAIN, 14));
		String[] lines = ImageUtils.splitString(text, prefLines, g, g.getFont(), splitDashes);
		int[] widths = new int[lines.length];
		FontMetrics fm = g.getFontMetrics();
		int boxHeight = fm.getHeight() * lines.length;
		int boxWidth = 0;
		for (int i = 0; i < lines.length; i++)
		{
			Rectangle2D bounds = fm.getStringBounds(lines[i], g);
			widths[i] = (int)Math.round(bounds.getWidth());
			boxWidth = Math.max(boxWidth, widths[i]);
		}
		g.setColor(bgColor);
		int tx = x;
		int ty = y;
		// draw the box and the words
		if (pos == FLOAT_SW)
		{
			tx = x - boxWidth;
		}
		else if (pos == FLOAT_NE)
		{
			ty = y - boxHeight;
		}
		else if (pos == FLOAT_NW)
		{
			tx = x - boxWidth;
			ty = y - boxHeight;
		}
		g.fillRect(tx - 2, ty - 1, boxWidth + 3, boxHeight + 2);
		g.setColor(textColor);
		g.drawRect(tx - 2, ty - 1, boxWidth + 3, boxHeight + 2);
		for (int i = 0; i < lines.length; i++)
		{
			g.drawString(lines[i], tx + (boxWidth - widths[i]) / 2,
					ty + fm.getHeight() * i + fm.getAscent());
		}
	}

	/**
	 * Helper method for adding to a render list
	 * @param obj the object to add
	 * @param x the x coordinate to add
	 * @param y the y coordinate to add
	 */
	private void addToRenderList(Object obj, int x, int y)
	{
		renderList.add(new Pair<Object, Point>(obj, new Point(x, y)));
	}

}
